#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <linux/inotify.h>
#include <sys/poll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "inotify-syscalls.h"
#include <time.h>
#include <pthread.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include "inotifyd.h"
using namespace std;

#define __REENTRANT
#define _EVENTS (IN_CLOSE_WRITE | IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_ATTRIB | IN_MOVE | IN_UNMOUNT | IN_MOVE_SELF)

extern int errno;
OutputBuffer *outputBuffer = NULL;
bool outOfWatches = false;

void term_handler (int signum) {
	fprintf(stderr, "Signal caught %d; terminating\n", signum);
	signal(signum, SIG_DFL);
	raise(signum);
}

int pruebainotifyd(int argc, char **argv) {
	signal(SIGINT, term_handler);
	signal(SIGTERM, term_handler);
	signal(SIGQUIT, term_handler);

	int daemon = -1;
	int watch = -1;
	bool usageHelp = false;

	// iterate through argv's
	for (int i = 0; i < argc; i++) {
		if (strcmp(argv[i], "-d") == 0) {
			daemon = i;
		} else if (strcmp(argv[i], "-w") == 0) {
			watch = i;
		} else if (strncmp(argv[i], "-", 1) == 0) {
			usageHelp = true;
		}
	}

	if (usageHelp) {
		printf("Usage: %s [-d [output]] [-w path ...]\n", argv[0]);
		printf("Listen for inotify events on all local mount point and outputs to standard output\n");
		printf("\t-d\t\trun as daemon, output to /tmp/inotifyd\n");
		printf("\t[output]\toutput to fifo pipe\n");
		printf("\t-w\t\twatch path instead of all local mount points\n");
		printf("\n");
		printf("Requires kernel support for inotify\n");
	} else {
		char *outputFilename;
		int watchStart = -1;
		int watchEnd = -1;
		if (daemon != -1) {
			if (daemon + 1 == argc || daemon + 1 == watch) {
				outputFilename = "/tmp/inotifyd";
			} else {
				outputFilename = argv[daemon+1];
			}
		} else {
			outputFilename = "";
		}

		// determine end-points of watch pathes
		if (watch != -1) {
			if (!(watch + 1 == argc || watch + 1 == daemon)) {
				watchStart = watch + 1;
				if (watch < daemon) {
					watchEnd = daemon;
				} else {
					watchEnd = argc;
				}
			}
		}

		// daemonize
		if (daemon != -1) {
			daemonize();
		}

		// start the process
		init(outputFilename, watchStart, watchEnd, argv);
	}
}

void daemonize() {
	pid_t pid = fork();

	if (pid < 0) {
	} else if (pid == 0) {
		// child
		setsid();
		// write pid to file
		FILE *pidFile = fopen("/var/run/inotifyd.pid", "w+");
		if (pidFile != NULL) {
			fprintf(pidFile, "%d\n", getpid());
			fflush(pidFile);
			fclose(pidFile);
		}
	} else {
		// parent
		exit(0);
	}
}

void init(char* outputFilename, int watchStart, int watchEnd, char** argv) {
	MountPoint *mp[128];
	memset(mp, 0, 128 * sizeof(MountPoint*));
	struct timespec sleep = {30, 0};

	outputBuffer = new OutputBuffer(outputFilename);
	outputBuffer->start();

	if (watchStart != -1) {
		// watch the paths from the command line
		for (int i = 0; i < watchEnd - watchStart && i < 128; i++) {
			mp[i] = new MountPoint("", argv[watchStart+i]);
			mp[i]->start();
		}

		while (1) {
			nanosleep(&sleep, NULL);
		}
	} else {
		// start the mtab watcher
		MtabWatch *mtab = new MtabWatch();
		int count;
		mtab->start();

		// start the tasks
		while (1) {
			// scan the file system
			char** path = getmtab(count);
			// do something here
			for (int i = 0; i < count; i+=2) {
				// compare to all currently mounted
				bool watched = false;
				int unused = -1;
				for (int j = 0; j < 128; j++) {
					if (mp[j] != NULL && strcmp(mp[j]->mountPointName,path[i+1]) == 0 && mp[j]->valid == true) {
						// currently being watched
						watched = true;
					} else if (mp[j] != NULL && mp[j]->valid == false) {
						// unmounted -- delete and clear
						delete mp[j];
						mp[j] = NULL;
					} else if (mp[j] == NULL && unused == -1) {
						unused = j;
					}
				}

				if (!watched) {
					if (unused == -1) {
						// exceeding 128 mount points
						fprintf(stderr, "Unable to watch %s\n", path[i+1]);
					} else {
						mp[unused] = new MountPoint(path[i], path[i+1]);
						mp[unused]->start();
					}
				}
				free((void*) path[i]);
				free((void*) path[i+1]);
			}
			free((void*) path);

			// wait for changes to mtab 
			mtab->waitForChange();
		}
	}
}

void *startScan(void* mount) {
	MountPoint *mp = (MountPoint*) mount;

	// scan directory structure
	mp->scanMount();
	pthread_exit(NULL);
}

void *startThread(void* mount) {
	MountPoint *mp = (MountPoint*) mount;

	// start directory scanner thread
	pthread_create(&(mp->scanThread), NULL, startScan, mp);
	pthread_detach(mp->scanThread);

	// start watching file descriptor for data from inotify
	mp->watchFileDescriptor();
	mp->valid = false;

	pthread_exit(NULL);
}

void *startPrinter(void* outputBuffer) {
	OutputBuffer *ob = (OutputBuffer*) outputBuffer;

	// start thread
	ob->run();
	pthread_exit(NULL);
}

void *startWatch(void* watcher) {
	MtabWatch *mt = (MtabWatch*) watcher;
	mt->run();
	pthread_exit(NULL);
}

MountPoint::MountPoint(char* device, char* mount) {
	valid = true;
	fd = -1;
	maxWD = -1;
	// copy name of mount point
	mountPointName = (char*) malloc((strlen(mount) + 1) * sizeof(char));
	strcpy(mountPointName, mount);
	deviceName = (char*) malloc((strlen(device) + 1) * sizeof(char));
	strcpy(deviceName, device);

	char *msg = (char*) malloc(strlen(mount) + strlen(device) + strlen("MOUNT") + 4);
	sprintf(msg, "MOUNT\t%s\t%s\n", device, mount);
	outputBuffer->enqueue(msg);

	// allocate space for directoryNames
	directoryNames = (dirhash_t **) malloc(sizeof(dirhash_t*) * hash);
	memset(directoryNames, 0, sizeof(dirhash_t*) * hash);
}

MountPoint::~MountPoint() {
	// unallocate the directory tree
	dirhash_t *ptr, *next;
	for (int i = 0; i < hash; i++) {
		ptr = directoryNames[i];
		while (ptr != NULL) {
			if (ptr->obj != NULL) {
				free((void*) ptr->obj->name);
				free((void*) ptr->obj);
			}
			next = ptr->next;
			free((void*) ptr);
			ptr = next;
		}
	}
	// free pointers
	free((void*) deviceName);
	free((void*) mountPointName);
	free((void*) directoryNames);
}

void MountPoint::start() {
	pthread_create(&watchThread, NULL, startThread, this);
	pthread_detach(watchThread);
}

void MountPoint::watchFileDescriptor() {
	inotify_event *event;
	int ret;
	int length;

	char buffer[16384];
	char *output;
	char *msg;
	char *fromName = NULL;

	dirmove_t *move = NULL;
	dirmove_t *nmove = NULL;

	int buffer_i;
	pollfd fdp;

	// wait for file descriptor to be set
	struct timespec sleep = {1, 0};
	while (fd == -1) {
		nanosleep(&sleep, NULL);
	}

	if (fd < 0) {
		// invalid fd
		return;
	}

	fprintf(stderr, "Starting watch of %s %d\n", mountPointName, watchThread);

	while (1) {
		// poll
		fdp.fd = fd;
		fdp.events = POLLIN;
		//ret = poll(&fdp, 1, 100);
		ret = poll(&fdp, 1, 100);

		if (ret < 0) {
			// error
		} else {
			if (fdp.revents == POLLNVAL) {
				break;
			} else if (fdp.revents == 0) {
				continue;
			}

			// read
			length = read(fd, buffer, sizeof(buffer));
			// array index
			buffer_i = 0;
			event = (inotify_event*) &buffer;
			while (move != NULL) {
				nmove = move->next;
				free((void*) move);
				move = nmove;
			}
			while (length - buffer_i > 0) {
				event = (inotify_event*) &buffer[buffer_i];
				switch (event->mask) {
					case IN_CLOSE_WRITE:
						output = "WRITE";
						break;
					case IN_CREATE:
						output = "CREATE";
						break;
					case IN_DELETE:
						output = "UNLINK";
						break;
					case IN_ATTRIB:
					case (IN_ISDIR | IN_ATTRIB):
						output = "CHMOD";
						break;
					case IN_MOVED_FROM:
						nmove = move;
						move = (dirmove_t*) malloc(sizeof(dirmove_t));
						memset(move, 0, sizeof(dirmove_t));
						move->next = nmove;
						move->cookie = event->cookie;
						move->from = event;
						output = NULL;
						// output = "UNLINK";
						break;
					case IN_MOVED_TO:
						while (move != NULL && move->cookie != event->cookie) {
							move = move->next;
						}
						if (move != NULL && move->cookie == event->cookie) {
							move->to = event;
							fromName = getFileName(move->from);
							output = "RENAME";
						} else {
							fprintf(stderr, "%s\n", "Move to without move from");
							output = NULL;
						}
						break;
					case (IN_ISDIR | IN_CREATE):
						output = "MKDIR";
						directoryEvent(event);
						break;
					case (IN_ISDIR | IN_DELETE):
						output = "RMDIR";
						break;
					case (IN_ISDIR | IN_MOVED_FROM):
						nmove = move;
						move = (dirmove_t*) malloc(sizeof(dirmove_t));
						memset(move, 0, sizeof(dirmove_t));
						move->next = nmove;
						move->cookie = event->cookie;
						move->from = event;
						output = NULL;
						break;
					case (IN_ISDIR | IN_MOVED_TO):
						while (move != NULL && move->cookie != event->cookie) {
							move = move->next;
						}
						if (move != NULL && move->cookie == event->cookie) {
							move->to = event;
						} else {
							fprintf(stderr, "%s\n", "Move to without move from");
						}
						output = NULL;
						break;
					case (IN_MOVE_SELF): {
						nmove = move;
						dirname_t* name = getDirEntry(event->wd);
						fromName = getDirectoryName(event->wd);
						if (name == NULL) {
							// ack?
							fprintf(stderr, "%s\n", "Invalid directory entry");
						} else {
							while (nmove != NULL && nmove->from != NULL && nmove->from->wd != name->parent && strcmp(name->name, nmove->from->name) != 0) {
								nmove = nmove->next;
							}
							if (nmove != NULL && nmove->from != NULL && nmove->to != NULL) {
								name->parent = nmove->to->wd;
								if (strcmp(nmove->from->name, nmove->to->name) != 0) {
									free((void*) name->name);
									name->name = (char*) malloc (sizeof(char) * (nmove->to->len + 2));
									strcpy(name->name, "/");
									strcat(name->name, nmove->to->name);
								}
							}
						}
						output = "RENAME";
						break;
					}
					case (IN_DELETE_SELF):
						if (directoryEvent(event) == IN_UNMOUNT) {
							output = "UMOUNT";
						} else {
							output = NULL;
						}
						break;
					case (IN_UNMOUNT):
						// close file descriptor
						fprintf(stderr, "Unmount on %s\n", mountPointName);
						output = "UMOUNT";
						close(fd);
						break;
					case (IN_Q_OVERFLOW):
						// print message on error
						output = NULL;
						fprintf(stderr, "Queue Overflow on file descriptor for %s\n", mountPointName);
						break;
					default:
						if (!(event->mask & IN_IGNORED)) {
							fprintf(stderr, "%x %d %d %s\n", event->mask, event->cookie, event->wd, (event->len ? event->name : ""));
						}
						if (event->mask & IN_UNMOUNT != 0) {
							// close file descriptor
							fprintf(stderr, "Unmount on %s\n", mountPointName);
							output = "UMOUNT";
							close(fd);
						} else {
							output = NULL;
						}
						break;
				}

				if (output != NULL) {
					char *filename = getFileName(event);
					int msgLen = strlen(output) + strlen(filename) + 3;
					if (fromName != NULL) {
						msgLen += strlen(fromName) + 1;
					}

					msg = (char*) malloc(sizeof(char) * msgLen);

					if (fromName != NULL) {
						sprintf(msg, "%s\t%s\t%s\n", output, fromName, filename);
						free((void*) fromName);
						fromName = NULL;
					} else {
						sprintf(msg, "%s\t%s\n", output, filename);
					}
					outputBuffer->enqueue(msg);
					free((void*) filename);
				}

				buffer_i += (sizeof(struct inotify_event) + sizeof(char) * event->len);
			}
		}
	}
	fprintf(stderr, "Ending watch of %s %d\n", mountPointName, watchThread);
}

char* MountPoint::getFileName(inotify_event *event) {
	char *directoryName = getDirectoryName(event->wd);
	char *filename;

	if (event->len) {
		int fnLength = strlen(directoryName) + event->len + 2;
		filename = (char*) malloc(sizeof(char) * fnLength);
		memset(filename, 0, sizeof(filename));
		strcpy(filename, directoryName);
		strcat(filename, "/");
		strncat(filename, event->name, event->len);
		free((void*) directoryName);
	} else {
		filename = directoryName;
	}

	return filename;
}

int MountPoint::directoryEvent(inotify_event *event) {
	char *directoryName;
	char *name;
	int wd;
	int parent;
	int ret = 0;

	switch (event->mask) {
		case (IN_ISDIR | IN_CREATE):
			// malloc & store
			directoryName = getDirectoryName(event->wd);	
			name = (char*) malloc((strlen(directoryName) + event->len + 2) * sizeof(char));
			memset(name, 0, sizeof(name));
			strcpy(name, directoryName);
			strcat(name, "/");
			strncat(name, event->name, event->len);

			wd = inotify_add_watch(fd, name, _EVENTS);
			free((void*) directoryName);
			free((void*) name);

			if (wd < 0) {
				// errors
			} else {
				char* name = (char*) malloc((event->len + 2) * sizeof(char));
				strcpy(name, "/");
				strcat(name, event->name);

				setDirEntry(wd, event->wd, event->len + 1, name);
			}
			break;
		case (IN_DELETE_SELF):
			removeDirEntry(event->wd, parent);
			if (parent == -1) {
				fprintf(stderr, "Unmount on %s\n", mountPointName);
				ret = IN_UNMOUNT;
				close(fd);
			}
			break;
		default:
			break;
	}
	return ret;
}

void MountPoint::scanMount() {
	char *path = mountPointName;
	fd = inotify_init();

	if (fd < 0) {
		// error;
		fprintf(stderr, "Unable to create inotify file descriptor\n");
	} else {
		DIR* dir_p = opendir(path);
		int dir_fd;
		int err;
		struct stat buf;
		char *name;
		if (dir_p != NULL) {
			dir_fd = dirfd(dir_p);
			err = fstat(dir_fd, &buf);
			if (err >= 0) {
				st_dev = buf.st_dev;
			} else {
				fprintf(stderr, "Unable to get st_dev from fstat\n");
			}

			if (strcmp(path, "/") != 0) {
				name = (char*) malloc((strlen(path) + 1) * sizeof(char));
				strcpy(name, path);
			} else {
				name = (char*) malloc(sizeof(char));
				memset(name, 0, sizeof(char));
			}
			close(dir_fd);

			scanDirectory(path, name, -1, -1);
		}
		closedir(dir_p);

		// watch directory structure for changes
		int idx = 0;
		int wd;
		struct timespec sleep = {5, 0};
		while (1) {
			dirname_t* cDir = getDirEntry(idx);
			if (cDir != NULL) {
				path = getDirectoryName(idx);
				name = cDir->name;
				wd = cDir->parent;

				scanDirectory(path, name, wd, 1);
				free((void*) path);
			}
			idx++;
			idx %= maxWD;
			nanosleep(&sleep, NULL);
		}
	}
}

void MountPoint::scanDirectory(char *path, char *name, int parent, int depth) {
	dirent* entry = NULL;
	DIR* dir_p = opendir(path);
	struct stat buf;
	int dir_fd;
	int err;
	int wd;

	if (dir_p != NULL) {
		dir_fd = dirfd(dir_p);
		err = fstat(dir_fd, &buf);

		if (buf.st_dev != st_dev || err < 0) {
			// directory is not on the same device
			closedir(dir_p);
			return;
		}

		wd = inotify_add_watch(fd, path, _EVENTS);
		// fprintf(stderr, "%d %s %s\n", wd, path, name);

		if (wd < 0) {
			// insufficient watch descriptors
			if (!outOfWatches) {
				fprintf(stderr, "Insufficient watch descriptors;  check /proc/sys/fs/inotify/max_user_watches\n");
				outOfWatches = true;
			}
		} else {
			// add to directory entry
			setDirEntry(wd, parent, strlen(name), name);

			if (depth != 0) {
				// scan directory
				entry = readdir(dir_p);
				while (entry != NULL) {
					if (entry->d_type == DT_DIR && strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")) {
						char *subdirPath = (char*) malloc((strlen(path) + strlen(entry->d_name) + 2) * sizeof(char));
						strcpy(subdirPath, path);
						strcat(subdirPath, "/");
						strcat(subdirPath, entry->d_name);
						char* subdirName = (char*) malloc((strlen(entry->d_name) + 2) * sizeof(char));
						strcpy(subdirName, "/");
						strcat(subdirName, entry->d_name);

						scanDirectory(subdirPath, subdirName, wd, (depth == -1 ? depth : depth - 1) );
						free((void*) subdirPath);
					}
					entry = readdir(dir_p);
				}
			}
			closedir(dir_p);
		}
	}
	return;
}

char* MountPoint::getDirectoryName(int idx) {
	int ptrSize = 128;
	int size = ptrSize;
	int pos = 0;
	char* ptr = (char*) malloc(size);
	memset(ptr, 0, size);

	if (idx < 0) {
		// event with no watch descriptor
	} else {
		recurseName(idx, ptr, pos, size);
		while (size == -1) {
			// not enough space in the buffer, reallocate and try again
			ptrSize *=2;
			ptr = (char*) realloc((void*) ptr, ptrSize);
			size = ptrSize;
			recurseName(idx, ptr, pos, size);
		}
	}

	return ptr;
}

void MountPoint::recurseName(int idx, char *buffer, int &cpos, int &size) {
	dirname_t *dEnt = getDirEntry(idx);
	if (dEnt == NULL) {
		memset(buffer, 0, size);
		cpos = 0;
	} else {
		if (dEnt->parent != -1) {
			recurseName(dEnt->parent, buffer, cpos, size);
		} else {
			memset(buffer, 0, size);
		}

		if (dEnt->length + cpos < size) {
			// and append!
			strcat(buffer, dEnt->name);
			cpos += dEnt->length;
		} else {
			// not enough space in the buffer
			size = -1;
		}
	}
}

dirname_t* MountPoint::getDirEntry(int idx) {
	// get hash table
	dirhash_t* ptr = directoryNames[idx % hash];
	while (ptr != NULL && ptr->idx != idx) {
		ptr = ptr->next;
	}

	if (ptr == NULL) {
		return NULL;
	} else {
		return ptr->obj;
	}
}

void MountPoint::setDirEntry(int idx, int parent, int length, char* name) {
	dirhash_t* ptr = directoryNames[idx % hash];
	dirhash_t* last = NULL;

	while (ptr != NULL && ptr->idx != idx) {
		last = ptr;
		ptr = ptr->next;
	}

	if (ptr != NULL && ptr->idx == idx) {
		// entry is already in the hash
		// unless name given is in the hash, free the pointer
		if (ptr->obj->name != name) {
			free((void*) name);
		}
	} else {
		dirhash_t* hashentry = (dirhash_t*) malloc(sizeof(dirhash_t));
		dirname_t* entry = (dirname_t*) malloc(sizeof(dirname_t));
		entry->name = name;
		entry->parent = parent;
		entry->length = length;
		hashentry->idx = idx;
		hashentry->obj = entry;
		hashentry->next = NULL;

		if (last == NULL) {
			// *ptr is NULL, set directoryNames[idx % hash]
			directoryNames[idx % hash] = hashentry;
		} else {
			last->next = hashentry;
		}

		if (idx > maxWD) {
			maxWD = idx;
		}
	}
}

bool MountPoint::removeDirEntry(int idx, int &parent) {
	dirhash_t* ptr = directoryNames[idx % hash];
	dirhash_t* last = NULL;

	while (ptr != NULL && ptr->idx != idx) {
		last = ptr;
		ptr = ptr->next;
	}

	if (ptr == NULL) {
		return false;
	} else {
		// change next pointer
		if (last != NULL) {
			last->next = ptr->next;
		} else {
			directoryNames[idx % hash] = ptr->next;
		}

		parent = ptr->obj->parent;

		// clear the space
		free((void*) ptr->obj->name);
		free((void*) ptr->obj);
		free((void*) ptr);
	}
	return true;
}

OutputBuffer::OutputBuffer(char* outputFileName) {
	head = 0;
	tail = 0;
	buffer = (char**) (malloc(sizeof(char*) * BUFFER_SIZE));

	memset(buffer, 0, sizeof(char*) * BUFFER_SIZE);
	memset(used, 0, sizeof(int) * BUFFER_SIZE);

	pthread_mutex_init(&lock, NULL);
	pthread_cond_init(&empty, NULL);

	if (strcmp(outputFileName, "") != 0) {
		// init output fifo
		struct stat buffer;
		int err = stat(outputFileName, &buffer);

		if (err == -1) {
			if (errno == ENOENT) {
				// create
				err = mkfifo(outputFileName, 00644);
			} else {
				fprintf(stderr, "Cannot stat %s; writing to standard output\n", outputFileName);
				outputFD = stdout;
				outputFN = NULL;
				return;
			}
		}

		int log = open(outputFileName, O_RDWR);
		if (log >= 0) {
			FILE *fd = fdopen(log, "r+");
			if (fd != NULL) {
				outputFD = fd;
				outputFN = outputFileName;
				if (!(buffer.st_mode & S_IFIFO)) {
					fprintf(stderr, "Warning: %s is not a fifo pipe\n", outputFileName);
				}
			} else {
				fprintf(stderr, "Cannot open file descriptor for %s; writing to standard output\n");
				outputFD = stdout;
				outputFN = NULL;
			}
		} else {
			fprintf(stderr, "Cannot open file %s for writing; writing to standard output\n", outputFileName);
			outputFD = stdout;
			outputFN = NULL;
		}
	} else {
		outputFD = stdout;
		outputFN = NULL;	
	}
}

OutputBuffer::~OutputBuffer() {
	for (int i = 0; i < BUFFER_SIZE; i++) {
		if (used[i]) {
			free((void*) buffer[i]);
		}
	}
	free((void*) buffer);
}

void OutputBuffer::enqueue(char* ptr) {
	pthread_mutex_lock(&lock);
	buffer[tail] = ptr;
	used[tail] = 1;

	if (head == tail) {
		// wake the printer
		pthread_cond_signal(&empty);
	}

	tail = (tail + 1) % BUFFER_SIZE;

	// check for wrap around
	if (tail == head) {
		if (used[head]) {
			free((void*) buffer[head]);
			used[head] = 0;
		}
		head = (head + 1) % BUFFER_SIZE;
	}
	pthread_mutex_unlock(&lock);
}

void OutputBuffer::start() {
	pthread_create(&thread, NULL, startPrinter, this);
	pthread_detach(thread);
}

void OutputBuffer::run() {
	char *ptr = NULL;
	while (1) {
		pthread_mutex_lock(&lock);
		while (head == tail) {
			pthread_cond_wait(&empty, &lock);
		}
		// dequeue
		ptr = buffer[head];
		used[head] = 0;
		head = (head + 1) % BUFFER_SIZE;

		// unlock
		pthread_mutex_unlock(&lock);
		if (ptr != NULL) {
			// block
			fprintf(outputFD, ptr);
			fflush(outputFD);
			free((void*) ptr);
		}
	}
}

MtabWatch::MtabWatch() {
	pthread_mutex_init(&lock, NULL);
	pthread_cond_init(&modified, NULL);
}

MtabWatch::~MtabWatch() {
}

void MtabWatch::start() {
	pthread_create(&thread, NULL, startWatch, this);
	pthread_detach(thread);
}

void MtabWatch::run() {
	// watch /etc
	inotify_event *event;
	int fd = inotify_init();
	int wd = inotify_add_watch(fd, "/etc", IN_CLOSE_WRITE);
	fprintf(stderr, "Starting watch of /etc/mtab\n");

	pollfd fdp;
	fdp.fd = fd;
	fdp.events = POLLIN;

	int ret;
	int length;
	char buffer[16384];
	int buffer_i;

	bool changed = false;

	while (1) {
		ret = poll(&fdp, 1, 500);

		if (ret < 0) {
			// error
		} else {
			if (fdp.revents == POLLNVAL) {
				break;
			} else if (fdp.revents == 0) {
				if (changed) {
					pthread_mutex_lock(&lock);
					pthread_cond_signal(&modified);
					pthread_mutex_unlock(&lock);
					changed = false;
				}
				continue;
			}

			// read
			length = read(fd, buffer, sizeof(buffer));
			// array index
			buffer_i = 0;
			event = (inotify_event*) &buffer;
			// fprintf(stderr, "%d\n", length);
			while (length - buffer_i > 0) {
				event = (inotify_event*) &buffer[buffer_i];
				switch (event->mask) {
					case IN_CLOSE_WRITE:
						if (event->len > 0 && strcmp(event->name, "mtab") == 0) {
							// wake the main thread
							changed = true;
						}
						break;
					default:
						break;
				}
				buffer_i += (sizeof(struct inotify_event) + sizeof(char) * event->len);
			}
		}
	}
}

void MtabWatch::waitForChange() {
	pthread_mutex_lock(&lock);
	pthread_cond_wait(&modified, &lock);
	pthread_mutex_unlock(&lock);
}

char** getmtab(int &count) {
	FILE* fd = fopen("/etc/mtab", "r");
	char line[1024], dev[1024], mp[1024], type[1024];
	int i = 0, limit = 64;
	char **ptr = (char**) malloc(limit * sizeof(char*));
	memset(line, 0, 1024);
	memset(dev, 0, 1024);
	memset(mp, 0, 1024);
	memset(type, 0, 1024);

	if (fd != NULL) {
		while (fgets(line, 1024, fd) != NULL) {
			if (sscanf(line, "%s %s %s %*s %*s %*s\n", &dev, &mp, &type) == 3) {
				if (i == limit) {
					limit = limit*2;
					ptr = (char**) realloc((void*) ptr, sizeof(char*) * limit);
				}

				if (strcmp(type, "ext3") == 0 || strcmp(type, "ext2") == 0) {
					ptr[i] = (char*) malloc(strlen(dev)+1);
					strcpy(ptr[i],dev);
					i++;
					ptr[i] = (char*) malloc(strlen(mp)+1);
					strcpy(ptr[i],mp);
					i++;
				}
			}
		}
		count = i;
		fclose(fd);
	} else {
		count = 0;
	}
	return ptr;
}

